
/**
 *	TrueMusic - simple Java music player
 *
 *	TrueMusic is free software; you can redistribute it
 *	and/or modify it under the terms of the GNU General
 *	Public License as published by the Free Software
 *	Foundation; either version 2 of the License or (at
 *	your option) any later version.
 *
 *	This program is distributed in the hope that it will be
 *	useful but WITHOUT ANY WARRANTY; without even the
 *	implied warranty of the MERCHANTABILITY or FITNESS FOR
 *	A PARTICULAR PURPOSE. See the GNU General Public
 *	License for more details.
 *
 *	Copyright (c)2005-2007 Ryan Macnak <rmacnak@gmail.com>
 */

package com.servequake.truemusic;

import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;

/**
 * Abstraction for consistent control behavior.
 */
public class Playlist extends DefaultHandler implements PlaybackListener, Iterable<Audio>, TableModel, ListModel
{
	private List<Audio> tracks = new ArrayList<Audio>();
	Audio current = null;
	
	private boolean random = false;
	private boolean repeat = false;

	private PlayerListener l;
	private int volume = -20;

	String name = "Playlist";

	public Playlist(String name, PlayerListener l)
	{
		this.name = name;
		this.l = l;
	}
	
	public boolean equals(Object o)
	{
		return name.equals(o.toString());
	}
	
	public String toString()
	{
		return name;
	}

	public void play()
	{
		if(current!=null) current.play();
	}

	public void pause()
	{
		if(current!=null) current.pause();
	}

	public void stop()
	{
		if(current!=null) current.stop();
	}

	public void next()
	{
		if(tracks.size()==0)
		{
			current = null;
			l.updateCurrent(null);
		}

		int next = tracks.indexOf(current);

		if(random)
		{
			int r = (int) (Math.random()*tracks.size());
			if(r==next) next++;
			else next = r;
		}
		else next++;

		if( next>=tracks.size() )
		{
			if(repeat) next = 0;
			else 
			{
				if(current!=null) current.stop();
				return;
			}
		}

		if(current!=null) current.stop();
		current = tracks.get(next);
		current.play();

		l.updateCurrent(current);
		return;
	}

	public void previous()
	{
		if(tracks.size()==0)
		{
			current = null;
			l.updateCurrent(null);
		}

		int next = tracks.indexOf(current);

		next--;

		if( next<0 ) return;

		if(current!=null) current.stop();
		current = tracks.get(next);
		current.play();

		l.updateCurrent(current);
		return;
	}

	public void setVolume(int level)
	{
		volume = level;
		return;
	}

	public void seek(int frame)
	{
		if(current!=null) current.seek(frame);
		return;
	}

	public void setTrack(int pos)
	{
		if(tracks.size()==0)
		{
			current = null;
			l.updateCurrent(null);
		}

		if( pos<0 || pos>=tracks.size() ) return;

		if(current!=null) current.stop();
		current = tracks.get(pos);
		current.play();

		l.updateCurrent(current);
	}

	public void setTrack(Audio a)
	{
		setTrack( tracks.indexOf(a) );
	}

	public void setRandom(boolean flag)
	{
		random = flag;
	}

	public void setRepeat(boolean flag)
	{
		repeat = flag;
	}

	public void load() throws Exception
	{
		// try
		// {
			// File f = new File("./" + name + ".obj");
			// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
			
			// tracks = (ArrayList<Audio>)ois.readObject();
			// ois.close();
			
			// for(Audio a: tracks) a.validate(this);
			
			// current = tracks.get(0);
			// tml.tableChanged( new TableModelEvent(this) );
			// l.updateCurrent(current);
		// }
		// catch(Exception e)
		// {
			this.addTrack( new File("./"+name+".m3u").toString() );
			//this.save(); //maybe we just needed to upgrade the obj file
		//}
	}

	public void save() throws IOException
	{
		// File f = new File("./" + name + ".obj");
		// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
			
		// oos.writeObject(tracks);
		// oos.flush(); oos.close();
		
		OutputStream os = new FileOutputStream( new File("./" + name + ".m3u"));
		PrintStream out = new PrintStream(os);
		for(Audio a:tracks) out.println( a.getResource() );
		out.close();
	}
	
	public void delete()
	{
		new File("./" + name + ".obj").delete();
	}
	

	//TODO: loop through Audio impl Classes
	public void addTrack(String res) throws Exception
	{
		String x = res.toLowerCase();
		Audio a = null;

		if(x.endsWith(".mp3"))
		{
			try
			{
				((Mp3Audio)(a = new Mp3Audio())).load(res,this);
			}
			catch(Exception e)
			{
				(a = new JavaLayerAudio()).load(res,this);
			}
		}
		else if(x.endsWith(".wav"))
		{
			(a = new JavaLayerAudio()).load(res,this);
		}
		else if(x.endsWith(".aif") || x.endsWith(".au"))
		{
			(a = new AppletClipAudio()).load(res,this);
		}
		else if(x.endsWith(".ogg") )
		{
			try
			{
				(a = new OggVorbisAudio()).load(res,this);
			}
			catch(Exception e)
			{
				(a = new OggFlacAudio()).load(res,this);
			}
		}
		else if(x.endsWith(".spx") )
		{
			(a = new JSpeexAudio()).load(res,this);
		}
		else if(x.endsWith(".flac") )
		{
			(a = new NativeFlacAudio()).load(res,this);
		}
		else if(x.endsWith(".m3u"))
		{
			File f = new File(res);
			InputStream is = new FileInputStream(f);
			BufferedReader in = new BufferedReader(new InputStreamReader(is));

			while(true)
			{
				String z = in.readLine();
				if(z==null)break;
				try
				{
					addTrack( z );
				}
				catch(Exception ex){}
			}

			in.close();
			return;
		}
		else return; //TODO: fail to try JavaLayerAudio?

		tracks.add(a);

		int pos = tracks.size() - 1;
		tml.tableChanged( new TableModelEvent(this) );
		ldl.intervalAdded(new ListDataEvent(this,ListDataEvent.INTERVAL_ADDED,0,0));
		
		if(current==null)
		{
			current = a;
			l.updateCurrent(current);
		}
		return;
	}
	
	public void removeTrack(int pos)
	{
		if(pos<0 || pos>=tracks.size()) return;

		Audio dead = tracks.get(pos);
		if(current==dead) next();

		tracks.remove(dead);
		tml.tableChanged( new TableModelEvent(this) );
		
		l.playlistChanged();
		return;
	}

	public void removeTrack(Audio a)
	{
		removeTrack( tracks.indexOf(a) );
	}
	
	public int currentIndex()
	{
		return tracks.indexOf(current);
	}
	
	public Audio getTrack(int index)
	{
		return tracks.get(index);
	}
//////////////////////////////

	public Iterator<Audio> iterator()
	{
		return tracks.iterator();
	}

///////////////////

	public void playbackStarted(){}
	public void playbackStopped(){}
	public void playbackError(){}

	public void frameCompleted(int frame)
	{
		l.frameCompleted(frame);
	}

	public void playbackCompleted()
	{
		next();
	}

	public int getVolume()
	{
		return volume;
	}

	/////////////////////////////////////
	
	TableModelListener tml = null;
	
	public void addTableModelListener(TableModelListener l)
	{
		tml = l;
	}
	
	public Class<?> getColumnClass(int columnIndex) {return String.class;}
	
	public int getColumnCount(){return 6;}
	
	static String[] col = {"Title","Artist", "Album","Track", "Genre","Format"};
	
	public String getColumnName(int index)
	{
		return col[index];
	}
	
	public int getRowCount()
	{
		return tracks.size();
	}
	
	public Object getValueAt(int row, int col)
	{
		if(col==0) return tracks.get(row).getTitle();
		if(col==1) return tracks.get(row).getArtist();
		if(col==2) return tracks.get(row).getAlbum();
		if(col==3) return tracks.get(row).getTrack();
		if(col==4) return tracks.get(row).getGenre();
		if(col==5) return tracks.get(row).formatName(); 
		
		return "Invalid column";
	}
	
	public boolean isCellEditable(int row, int col)
	{
		return false;
	}
	
	public void removeTableModelListener(TableModelListener l)
	{
		if(tml==l)tml=null;
	}
	
	public void setValueAt(Object aValue, int row, int col)
	{
		//TODO: tag editing avaible here?
	}
	
	/////////////////////////
	
	private ListDataListener ldl;

	public void addListDataListener(ListDataListener l)
	{
		this.ldl = l;
	}

	public Object getElementAt(int index)
	{
		return tracks.get(index);
	}

	public int getSize()
	{
		return tracks.size();
	}

	public void removeListDataListener(ListDataListener l)
	{
		ldl = null;
	}
}

